package connectors.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.TreeMap;

import model.Expense;
import model.Income;
import model.Transfer;
import connectors.DataSource;

public class BinaryConnector implements DataSource {
	private LinkedList<Expense> expenses;
	private LinkedList<Income> incomes;
	private LinkedList<Transfer> transfers;

	private LinkedList<String> categories;
	private int defaultCategory = 4;

	private LinkedList<String> accounts;
	private int defaultAccount = 1;

	
	@Override
	public void initialize(File location) {		
		if(location == null || !location.exists()) {
			readDefaults();
		}
		else {
			read(location);
		}		
	}

	@Override
	public void addExpense(Expense exp) {
		expenses.add(exp);
	}
	
	@Override
	public void removeExpense(Expense expense) {
		if(expenses.remove(expense)) {
			System.out.println("remove succeeded");
		}
	}

	@Override
	public void addIncome(Income income) {
		incomes.add(income);
	}
	
	@Override
	public void removeIncome(Income income) {
		if(incomes.remove(income)) {
			System.out.println("income removed");
		}		
	}
	
	@Override
	public void addTransfer(Transfer transfer) {
		transfers.add(transfer);
	}

	@Override
	public LinkedList<Expense> getDayExpenses(int year, int month, int day,
			Integer category, Integer account) {
		LinkedList<Expense> daily = new LinkedList<Expense>();

		for (Expense expense : expenses) {
			if (expense.matchesDay(year, month, day)
					&& expense.matchesCategory(category)
					&& expense.matchesAccount(account))
				daily.add(expense);
		}
		return daily;
	}

	@Override
	public LinkedList<Income> getDayIncome(int year, int month, int day,
			Integer account) {
		LinkedList<Income> daily = new LinkedList<Income>();

		for (Income income : incomes) {
			if (income.matchesDay(year, month, day)
					&& income.matchesAccount(account))
				daily.add(income);
		}
		return daily;
	}

	@Override
	public LinkedList<Expense> getMonthExpenses(int year, int month,
			Integer category, Integer account) {
		LinkedList<Expense> monthly = new LinkedList<Expense>();

		for (Expense expense : expenses) {
			if (expense.matchesMonth(year, month)
					&& expense.matchesCategory(category)
					&& expense.matchesAccount(account))
				monthly.add(expense);
		}

		return monthly;
	}

	@Override
	public LinkedList<Income> getMonthIncome(int year, int month,
			Integer account) {
		LinkedList<Income> monthly = new LinkedList<Income>();

		for (Income income : incomes) {
			if (income.matchesMonth(year, month)
					&& income.matchesAccount(account))
				monthly.add(income);
		}
		return monthly;
	}

	@Override
	public LinkedList<Expense> getYearExpenses(int year, Integer category,
			Integer account) {
		LinkedList<Expense> yearly = new LinkedList<Expense>();

		for (Expense expense : expenses) {
			if (expense.matchesYear(year) && expense.matchesCategory(category)
					&& expense.matchesAccount(account))
				yearly.add(expense);
		}

		return yearly;
	}

	@Override
	public LinkedList<Income> getYearIncome(int year, Integer account) {
		LinkedList<Income> yearly = new LinkedList<Income>();

		for (Income income : incomes) {
			if (income.matchesYear(year) && income.matchesAccount(account))
				yearly.add(income);
		}
		return yearly;
	}

	@Override
	public Object[] getCoveredYears() {
		HashSet<Integer> years = new HashSet<Integer>();
		for (Expense expense : expenses) {
			years.add(expense.getYear());
		}

		return years.toArray();
	}

	@Override
	public LinkedList<String> getCategories() {
		return categories;
	}

	@Override
	public void setCategories(LinkedList<String> categories) {
		this.categories = categories;
	}

	@Override
	public int getDefaultCategory() {
		return defaultCategory;
	}

	@Override
	public Integer getDefaultCategoryAsInteger() {
		return new Integer(getDefaultCategory());
	}

	@Override
	public void setDefaultCategory(int defaultCategory) {
		this.defaultCategory = defaultCategory;
	}

	public LinkedList<String> getAccounts() {
		return accounts;
	}

	@Override
	public String[] getPopularVenues() {
		HashMap<String, Integer> tags = new HashMap<String, Integer>();

		for (Expense expense : expenses) {
			String v = expense.getVenue();
			if (tags.containsKey(v)) {
				tags.put(v, tags.get(v) + 1);
			} else {
				tags.put(v, 1);
			}
		}
		// TODO implement different list for income

		ValueComparator comparator = new ValueComparator(tags);
		TreeMap<String, Integer> tagsSorted = new TreeMap<String, Integer>(comparator);
		tagsSorted.putAll(tags);
		
//		for (String tag : tagsSorted.keySet()) {
//			System.out.println("tag-"+tag+":"+tags.get(tag));
//		}
		return tags.keySet().toArray(new String[0]);
	}

	class ValueComparator implements Comparator<String> {
		HashMap<String, Integer> base;
		
		public ValueComparator(HashMap<String, Integer> base) {
			this.base = base;
		}

		public int compare(String a, String b) {
			if (base.get(a) >= base.get(b)) {
				return -1;
			} else {
				return 1;
			} // returning 0 would merge keys
		}
	}

	@Override
	public void setAccounts(LinkedList<String> accounts) {
		this.accounts = accounts;
		
	}

	@Override
	public int getDefaultAccount() {
		return defaultAccount;
	}

	@Override
	public Integer getDefaultAccountAsInteger() {
		return new Integer(getDefaultAccount());
	}

	@Override
	public void setDefaultAccount(int defaultAccount) {
		this.defaultAccount = defaultAccount;
		
	}

	@Override
	public void store(File location) {
		OutputStream fos = null;

		try {
			fos = new FileOutputStream(location);
			ObjectOutputStream o = new ObjectOutputStream(fos);

			o.writeObject(expenses);
			o.writeObject(incomes);
			o.writeObject(transfers);
			
			o.writeObject(categories);
			o.writeInt(defaultCategory);
			
			o.writeObject(accounts);
			o.writeInt(defaultAccount);
			o.close();
			
		} catch (IOException e) {
			System.err.println(e);
		} finally {
			try {
				fos.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public void read(File location) {
		InputStream fis = null;

		try {
			fis = new FileInputStream(location);

			ObjectInputStream o = new ObjectInputStream(fis);
			
			expenses = (LinkedList<Expense>) o.readObject();
			incomes = (LinkedList<Income>) o.readObject();
			transfers = (LinkedList<Transfer>) o.readObject();
			
			categories = (LinkedList<String>) o.readObject();
			defaultCategory = o.readInt();
			
			accounts = (LinkedList<String>) o.readObject();
			defaultAccount = o.readInt();
			
			o.close();			
		} catch (IOException e) {
			System.err.println(e);
		} catch (ClassNotFoundException e) {
			System.out.println("class not found");
			e.printStackTrace();
		} finally {
			try {
				fis.close();
			} catch (Exception e) {
			}
		}
		
	}
	
	@Override
	public void readDefaults() {
		expenses = new LinkedList<Expense>();
		incomes = new LinkedList<Income>();
		transfers = new LinkedList<Transfer>();

		categories = new LinkedList<String>();
		categories.add("Lehrmittel");
		categories.add("Lebensmittel");
		categories.add("Wohnungskosten");
		categories.add("Freizeit");
		categories.add("Sonstiges");
		categories.add("Fahrtkosten");

		accounts = new LinkedList<String>();
		accounts.add("Girokonto");
		accounts.add("Bargeld");
		accounts.add("Athenecard");		
	}

	@Override
	public LinkedList<Object> getRecentTransactions() {
		LinkedList<Object> all = new LinkedList<Object>();
		
		// TODO make true
		
		all.addAll(expenses);
		all.addAll(incomes);
		all.addAll(transfers);
		
		return all;
	}

	
	
}
